package org.springframework.security.config.annotation.web.configuration;

import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
import org.springframework.security.config.annotation.authentication.configurers.provisioning.JdbcUserDetailsManagerConfigurer;
import org.springframework.security.config.annotation.authentication.configurers.userdetails.DaoAuthenticationConfigurer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.DefaultLoginPageConfigurer;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
import org.springframework.web.accept.ContentNegotiationStrategy;
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
import org.springframework.web.cors.CorsConfigurationSource;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static org.springframework.security.config.Customizer.withDefaults;

/**
 * @author Dillon
 * @date 2024/7/15
 * @slogan 致敬大师 致敬未来的你
 * @desc 核心配置类，通过使用spring的 @configuration 注解进行所有的httpSecurity 配置
 */
@Configuration(proxyBeanMethods = false)
class HttpSecurityConfiguration {

	/**
	 * 名称前缀
	 */
	private static final String BEAN_NAME_PREFIX = "org.springframework.security.config.annotation.web.configuration.HttpSecurityConfiguration.";

	/**
	 * 默认配置bean名称 httpSecurity
	 */
	private static final String HTTP_SECURITY_BEAN_NAME = BEAN_NAME_PREFIX + "httpSecurity";

	/**
	 * security所有的后置处理器
	 */
	private ObjectPostProcessor<Object> objectPostProcessor;

	/**
	 * 默认的父类认证管理器认证配置器
	 */
	private AuthenticationConfiguration authenticationConfiguration;

	/**
	 * spring上下文
	 */
	private ApplicationContext context;

	/**
	 * security缓存策略实现
	 */
	private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
			.getContextHolderStrategy();

	private ContentNegotiationStrategy contentNegotiationStrategy = new HeaderContentNegotiationStrategy();

	/**
	 * 通过自动注入获取 security的后置处理器
	 *
	 * @param objectPostProcessor 后置处理器
	 */
	@Autowired
	void setObjectPostProcessor(ObjectPostProcessor<Object> objectPostProcessor) {
		this.objectPostProcessor = objectPostProcessor;
	}

	/**
	 * 自动注入
	 *
	 * @param authenticationConfiguration 认证配置器
	 */
	@Autowired
	void setAuthenticationConfiguration(AuthenticationConfiguration authenticationConfiguration) {
		this.authenticationConfiguration = authenticationConfiguration;
	}

	/**
	 * 自动注入
	 *
	 * @param context 上下文对象
	 */
	@Autowired
	void setApplicationContext(ApplicationContext context) {
		this.context = context;
	}

	/**
	 * 自动注入
	 *
	 * @param securityContextHolderStrategy 自定义认证缓存策略
	 */
	@Autowired(required = false)
	void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
		this.securityContextHolderStrategy = securityContextHolderStrategy;
	}

	@Autowired(required = false)
	void setContentNegotiationStrategy(ContentNegotiationStrategy contentNegotiationStrategy) {
		this.contentNegotiationStrategy = contentNegotiationStrategy;
	}

	/**
	 * 核心方法，构建httpSecurity
	 *
	 * @return HttpSecurity
	 * @throws Exception 构建异常
	 */
	@Bean(HTTP_SECURITY_BEAN_NAME)
	@Scope("prototype")
	HttpSecurity httpSecurity() throws Exception {
		// 构建加密实现类懒加载
		LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(this.context);
		// 默认的认证管理器构建器
		AuthenticationManagerBuilder authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(
				this.objectPostProcessor, passwordEncoder);
		// 父认证管理器设置
		authenticationBuilder.parentAuthenticationManager(authenticationManager());
		// 认证事件发布器
		authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher());
		// new HttpSecurity
		HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
		// 初始化异步请求过滤器并设置值
		WebAsyncManagerIntegrationFilter webAsyncManagerIntegrationFilter = new WebAsyncManagerIntegrationFilter();
		webAsyncManagerIntegrationFilter.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
		// @formatter:off
		// 默认过滤器配置
		http
			.csrf(withDefaults())
			.addFilter(webAsyncManagerIntegrationFilter)
			.exceptionHandling(withDefaults())
			.headers(withDefaults())
			.sessionManagement(withDefaults())
			.securityContext(withDefaults())
			.requestCache(withDefaults())
			.anonymous(withDefaults())
			.servletApi(withDefaults())
			.apply(new DefaultLoginPageConfigurer<>());
		http.logout(withDefaults());
		// @formatter:on
		applyCorsIfAvailable(http);
		applyDefaultConfigurers(http);
		return http;
	}

	/**
	 * 自定义的cors 跨域配置
	 *
	 * @param http HttpSecurity
	 * @throws Exception 执行异常
	 */
	private void applyCorsIfAvailable(HttpSecurity http) throws Exception {
		String[] beanNames = this.context.getBeanNamesForType(CorsConfigurationSource.class);
		if (beanNames.length == 1) {
			http.cors(withDefaults());
		}
	}

	/**
	 * 获取配置的父类认证管理器
	 *
	 * @return AuthenticationManager
	 * @throws Exception 执行异常
	 */
	private AuthenticationManager authenticationManager() throws Exception {
		return this.authenticationConfiguration.getAuthenticationManager();
	}

	/**
	 * 获取认证事件发布器
	 *
	 * @return AuthenticationEventPublisher
	 */
	private AuthenticationEventPublisher getAuthenticationEventPublisher() {
		// 如果配置认证事件发布器 则使用配置的
		// 否则使用默认的认证事件发布器
		if (this.context.getBeanNamesForType(AuthenticationEventPublisher.class).length > 0) {
			return this.context.getBean(AuthenticationEventPublisher.class);
		}
		return this.objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher());
	}

	/**
	 * 自定义 继承 AbstractHttpConfigurer 类也会被加入过滤器中
	 *
	 * @param http HttpSecurity
	 * @throws Exception 执行异常
	 */
	private void applyDefaultConfigurers(HttpSecurity http) throws Exception {
		ClassLoader classLoader = this.context.getClassLoader();
		List<AbstractHttpConfigurer> defaultHttpConfigurers = SpringFactoriesLoader
				.loadFactories(AbstractHttpConfigurer.class, classLoader);
		for (AbstractHttpConfigurer configurer : defaultHttpConfigurers) {
			http.apply(configurer);
		}
	}

	/**
	 * 创建默认共享对象
	 * 直接将spring上下文放进去
	 *
	 * @return 共享对象MAP
	 */
	private Map<Class<?>, Object> createSharedObjects() {
		Map<Class<?>, Object> sharedObjects = new HashMap<>();
		sharedObjects.put(ApplicationContext.class, this.context);
		sharedObjects.put(ContentNegotiationStrategy.class, this.contentNegotiationStrategy);
		return sharedObjects;
	}

	/**
	 * 默认的认证管理器构建器实现类
	 * 主要设置加密实现类
	 */
	static class DefaultPasswordEncoderAuthenticationManagerBuilder extends AuthenticationManagerBuilder {

		/**
		 * 默认的加密实现类
		 */
		private final PasswordEncoder defaultPasswordEncoder;

		/**
		 * 构造函数
		 *
		 * @param objectPostProcessor    对象后处理器
		 * @param defaultPasswordEncoder 加密实现类
		 */
		DefaultPasswordEncoderAuthenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor,
				PasswordEncoder defaultPasswordEncoder) {
			super(objectPostProcessor);
			this.defaultPasswordEncoder = defaultPasswordEncoder;
		}

		/**
		 * 便捷配置类设置 设置使用内存作为登录认证配置
		 *
		 * @return 认证管理器
		 * @throws Exception 执行异常
		 */
		@Override
		public InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder> inMemoryAuthentication() throws Exception {
			return super.inMemoryAuthentication().passwordEncoder(this.defaultPasswordEncoder);
		}

		/**
		 * 便捷配置类设置 设置使用数据库作为登录认证配置
		 *
		 * @return 认证管理器
		 * @throws Exception 执行异常
		 */
		@Override
		public JdbcUserDetailsManagerConfigurer<AuthenticationManagerBuilder> jdbcAuthentication() throws Exception {
			return super.jdbcAuthentication().passwordEncoder(this.defaultPasswordEncoder);
		}

		/**
		 * 便捷配置类设置 设置使用数据源 数据库 内存 等作为登录认证配置
		 *
		 * @param userDetailsService 自定义的userDetailsService实现类
		 * @param <T>                认证数据查询来源接口 泛型
		 * @return userDetailsService
		 * @throws Exception 执行异常
		 */
		@Override
		public <T extends UserDetailsService> DaoAuthenticationConfigurer<AuthenticationManagerBuilder, T> userDetailsService(
				T userDetailsService) throws Exception {
			return super.userDetailsService(userDetailsService).passwordEncoder(this.defaultPasswordEncoder);
		}

	}

	/**
	 * 自定义的懒加载加密配置类
	 * 只有security调用加密实现时第一次从spring中获取加密并执行代理方法
	 */
	static class LazyPasswordEncoder implements PasswordEncoder {

		/**
		 * spring 上下文
		 */
		private final ApplicationContext applicationContext;

		/**
		 * 加密实现类
		 */
		private PasswordEncoder passwordEncoder;

		/**
		 * 构造函数
		 *
		 * @param applicationContext spring上下文
		 */
		LazyPasswordEncoder(ApplicationContext applicationContext) {
			this.applicationContext = applicationContext;
		}

		/**
		 * 加密
		 *
		 * @param rawPassword 原密码
		 * @return 加密后数据
		 */
		@Override
		public String encode(CharSequence rawPassword) {
			return getPasswordEncoder().encode(rawPassword);
		}

		/**
		 * 密码是否匹配
		 *
		 * @param rawPassword     原密码
		 * @param encodedPassword 加密后密码
		 * @return 密码是否匹配
		 */
		@Override
		public boolean matches(CharSequence rawPassword, String encodedPassword) {
			return getPasswordEncoder().matches(rawPassword, encodedPassword);
		}

		/**
		 * 更新密码
		 *
		 * @param encodedPassword 加密密码
		 * @return 密码是否更新成功
		 */
		@Override
		public boolean upgradeEncoding(String encodedPassword) {
			return getPasswordEncoder().upgradeEncoding(encodedPassword);
		}

		/**
		 * 获取加密实现类
		 *
		 * @return PasswordEncoder
		 */
		private PasswordEncoder getPasswordEncoder() {
			// 优先使用此处属性配置的加密类
			if (this.passwordEncoder != null) {
				return this.passwordEncoder;
			}
			// 其次使用spring注入的bean的加密实现类
			PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
			// 最后初始化默认的加密实现类
			if (passwordEncoder == null) {
				passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
			}
			this.passwordEncoder = passwordEncoder;
			return passwordEncoder;
		}

		/**
		 * applicationContext.getBean(type)
		 *
		 * @param type class类
		 * @param <T>  泛型
		 * @return bean
		 */
		private <T> T getBeanOrNull(Class<T> type) {
			try {
				return this.applicationContext.getBean(type);
			} catch (NoSuchBeanDefinitionException ex) {
				return null;
			}
		}

		@Override
		public String toString() {
			return getPasswordEncoder().toString();
		}

	}

}
